﻿using System;
using System.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.Objects;
using IniTechCaash.Domain.Entities;
using IniTechCaash.Domain.EntityFramework;
using IniTechCaash.Domain.Services;
using IniTechCaash.WebApplication.Models;
using FluentValidation;
using FluentValidation.Results;
using System.IO;

namespace IniTechCaash.WebApplication.Controllers
{
    public class JournalController : Controller
    {
        private const string UPLOAD_DIR = "~/SourceDocuments/";

        private CaashContext db;
        public JournalController()
        {
            db = new CaashContext();
        }

        [Authorize(Roles = "Manager, User")]
        public ActionResult Create()
        {
            JournalEntryCreateViewModel model = new JournalEntryCreateViewModel
            {
                Lines = new List<JournalEntryLineCreateViewModel>(),
            };
            model.Lines.Add(new JournalEntryLineCreateViewModel
            {
                Date = DateTime.Now.Date,
                Side = NormalSide.Debit,
            });
            model.Lines.Add(new JournalEntryLineCreateViewModel
            {
                Date = DateTime.Now.Date,
                Side = NormalSide.Credit,
            });

            var acccounts = db.Accounts.Where(a => a.IsEnabled);
            var accountModels = new List<FinanceAccountDetailsViewModel>();
            foreach (var a in acccounts)
                accountModels.Add(new FinanceAccountDetailsViewModel(a));
            foreach (var line in model.Lines)
            {
                line.AccountSelectList = new SelectList(accountModels, "Id", "CompositeId");
            }

            return View(model);
        }

        [Authorize(Roles = "Manager, User")]
        [HttpPost]
        public ActionResult Create(JournalEntryCreateViewModel model)
        {
            ValidationResult result = new JournalEntryCreateViewModel.Validator().Validate(model);
            if (model.SourceDocument != null && model.SourceDocument.ContentLength > 0 && model.SourceDocument.ContentType != "application/pdf")
                ModelState.AddModelError("SourceDocument", "SourceDocument must be a pdf file.");
            if (!ModelState.IsValid || !result.IsValid)
            {
                foreach (var e in result.Errors)
                    ModelState.AddModelError("", e.ErrorMessage);
                var acccounts = db.Accounts.Where(a => a.IsEnabled);
                var accountModels = new List<FinanceAccountDetailsViewModel>();
                foreach (var a in acccounts)
                    accountModels.Add(new FinanceAccountDetailsViewModel(a));
                foreach (var line in model.Lines)
                {
                    line.AccountSelectList = new SelectList(accountModels, "Id", "CompositeId");
                }

                return View(model);
            }

            JournalEntry entry = model.GetJournalEntry(User.Identity.Name);
            entry.Lines = entry.Lines.OrderByDescending(l => l.Side).ToList();

            DateTime timeStamp = DateTime.Now;
            for (int i = 0; i < model.Lines.Count; ++i)
            {
                int accountId = model.Lines[i].AccountId;
                entry.Lines[i].Account = db.Accounts.First(a => a.Id == accountId);
                entry.Lines[i].LastModifiedBy = User.Identity.Name;
                entry.Lines[i].LastModifiedDate = timeStamp;
            }

            entry.CreatedBy = entry.LastModifiedBy = User.Identity.Name;
            entry.CreatedDate = entry.LastModifiedDate = timeStamp;
            db.JournalEntries.Add(entry);
            db.SaveChanges();

            //if (model.SourceDocument != null && model.SourceDocument.ContentLength > 0)
            //{
            //    var fileName = Path.GetFileName(entry.Id + "_" + model.SourceDocument.FileName);
            //    var path = Path.Combine(Server.MapPath(UPLOAD_DIR), fileName);
            //    var fileInfo = new FileInfo(path);
            //    fileInfo.Directory.Create();
            //    model.SourceDocument.SaveAs(path);
            //}

            return RedirectToAction("Details", new { id = entry.Id });
        }

        [Authorize(Roles = "Manager, User")]
        public ActionResult Delete(int id)
        {
            var entry = db.JournalEntries.First(je => je.Id == id);
            for (int i = entry.Lines.Count - 1; i >= 0; --i)
                db.Entry<JournalEntryLine>(entry.Lines[i]).State = EntityState.Deleted;
            db.Entry<JournalEntry>(entry).State = EntityState.Deleted;
            db.SaveChanges();

            // delete potential existing file
            var path = Server.MapPath(UPLOAD_DIR);
            var file = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Select(Path.GetFileName).FirstOrDefault(f => f.StartsWith(id.ToString() + "_"));
            if (file != null)
                System.IO.File.Delete(Path.Combine(path, file)); 

            return RedirectToAction("Index");
        }

        [Authorize(Roles = "Manager, User")]
        public ActionResult Details(int id)
        {
            var entry = db.JournalEntries.First(je => je.Id == id);
            var entryModel = new JournalEntryDetailsViewModel(entry);

            //var path = Server.MapPath(UPLOAD_DIR);
            //var fileNames = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Select(Path.GetFileName);
            //var file = fileNames.FirstOrDefault(f => f.StartsWith(entry.Id.ToString()));
            //if (file != null)
            //{
            //    int idStringLen = entry.Id.ToString().Length;
            //    entryModel.SourceDocument = file.Substring(idStringLen + 1, file.Length - idStringLen - 1);
            //}

            var activityLogs = db.ActivityLogs.Where(al => al.ObjectReferenceType == "JournalEntry" && al.ObjectReferenceId == entry.Id);
            entryModel.ActivityLogs = activityLogs.ToList();
            entryModel.ActivityLogs.Insert(0, new ActivityLog
            {
                CreatedBy = entry.CreatedBy,
                LogDate = entry.CreatedDate,
                Message = "Created.",
                ObjectReferenceId = entry.Id,
                ObjectReferenceType = "JournalEntry",
            });

            return View(entryModel);
        }

        [Authorize(Roles = "Manager, User")]
        public ActionResult Edit(int id)
        {
            var entry = db.JournalEntries.First(je => je.Id == id);
            JournalEntryEditViewModel model = new JournalEntryEditViewModel(entry);
            model.Lines = new List<JournalEntryLineEditViewModel>();
            foreach (var line in entry.Lines)
                model.Lines.Add(new JournalEntryLineEditViewModel(line));

            var acccounts = db.Accounts.Where(a => a.IsEnabled);
            var accountModels = new List<FinanceAccountDetailsViewModel>();
            foreach (var a in acccounts)
                accountModels.Add(new FinanceAccountDetailsViewModel(a));
            foreach (var line in model.Lines)
            {
                line.AccountSelectList = new SelectList(accountModels, "Id", "CompositeId", accountModels.First(am => am.Id == line.AccountId));
            }

            return View(model);
        }

        [Authorize(Roles = "Manager, User")]
        [HttpPost]
        public ActionResult Edit(JournalEntryEditViewModel model)
        {
            ValidationResult result = new JournalEntryEditViewModel.Validator().Validate(model);
            if (model.SourceDocument != null && model.SourceDocument.ContentLength > 0 && model.SourceDocument.ContentType != "application/pdf")
                ModelState.AddModelError("SourceDocument", "SourceDocument must be a pdf file.");
            if (!ModelState.IsValid || !result.IsValid)
            {
                foreach (var e in result.Errors)
                    ModelState.AddModelError("", e.ErrorMessage);

                var acccounts = db.Accounts.Where(a => a.IsEnabled);
                var accountModels = new List<FinanceAccountDetailsViewModel>();
                foreach (var a in acccounts)
                    accountModels.Add(new FinanceAccountDetailsViewModel(a));
                foreach (var line in model.Lines)
                {
                    line.AccountSelectList = new SelectList(accountModels, "Id", "CompositeId", accountModels.First(am => am.Id == line.AccountId));
                }

                return View(model);
            }

            JournalEntry entry = db.JournalEntries.First(je => je.Id == model.Id);
            for (int i = entry.Lines.Count - 1; i >= 0; --i)
                db.Entry<JournalEntryLine>(entry.Lines[i]).State = EntityState.Deleted;
            db.SaveChanges();

            entry = model.GetJournalEntry(entry, User.Identity.Name);
            entry.Lines = entry.Lines.OrderByDescending(l => l.Side).ToList();

            DateTime timeStamp = DateTime.Now;
            for (int i = 0; i < model.Lines.Count; ++i)
            {
                int accountId = model.Lines[i].AccountId;
                entry.Lines[i].Account = db.Accounts.First(a => a.Id == accountId);
                entry.Lines[i].LastModifiedBy = User.Identity.Name;
                entry.Lines[i].LastModifiedDate = timeStamp;
            }

            entry.LastModifiedBy = User.Identity.Name;
            entry.LastModifiedDate = timeStamp;
            db.Entry<JournalEntry>(entry).State = EntityState.Modified;
            db.SaveChanges();

            ActivityLog log = new ActivityLog
            {
                CreatedBy = User.Identity.Name,
                LogDate = DateTime.Now,
                Message = "Modified.",
                ObjectReferenceId = entry.Id,
                ObjectReferenceType = "JournalEntry",
            };
            db.ActivityLogs.Add(log);
            db.Entry<ActivityLog>(log).State = EntityState.Added;
            db.SaveChanges();

            if (model.SourceDocument != null && model.SourceDocument.ContentLength > 0)
            {
                // delete potential existing file
                var path = Server.MapPath(UPLOAD_DIR);
                var file = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Select(Path.GetFileName).FirstOrDefault(f => f.StartsWith(model.Id.ToString() + "_"));
                if (file != null)
                    System.IO.File.Delete(Path.Combine(path, file)); 
                // create new file
                var fileName = Path.GetFileName(entry.Id + "_" + model.SourceDocument.FileName);
                var fileInfo = new FileInfo(path);
                fileInfo.Directory.Create();
                model.SourceDocument.SaveAs(Path.Combine(path, fileName));
            }

            return RedirectToAction("Details", new { id = entry.Id });
        }

        [Authorize(Roles = "Admin, Manager, User")]
        public ActionResult Index(JournalViewModel model = null)
        {
            var entries = db.JournalEntries.OrderByDescending(e => e.Id).AsEnumerable();
            List<JournalEntryDetailsViewModel> models = new List<JournalEntryDetailsViewModel>(entries.Count());
            foreach (var e in entries)
                models.Add(new JournalEntryDetailsViewModel(e));
            if (model != null)
            {
                if (model.EndDate.HasValue && model.StartDate.HasValue)
                {
                    if (model.EndDate.Value <= model.StartDate.Value)
                    {
                        ModelState.AddModelError("EndDate", "End date must be after Start date.");
                        return View(model);
                    }
                }

                if (model.EndDate.HasValue)
                    models = models.Where(m => m.Lines.Any(l => l.Date <= model.EndDate.Value)).ToList();
                if (model.StartDate.HasValue)
                    models = models.Where(m => m.Lines.Any(l => l.Date >= model.StartDate.Value)).ToList();
                if (!model.ShowPosted)
                    models = models.Where(m => m.IsPosted == false).ToList();
                if (!model.ShowNonPosted)
                    models = models.Where(m => m.IsPosted == true).ToList();
                model.JournalEntries = models;
            }
            else
            {
                model = new JournalViewModel
                {
                    JournalEntries = models,
                    ShowNonPosted = true,
                    ShowPosted = true,
                };
            }
            return View(model);
        }

        [Authorize(Roles = "Manager")]
        public ActionResult Post(int id)
        {
            var entry = db.JournalEntries.Include(je => je.Lines).First(je => je.Id == id);
            entry.DatePosted = DateTime.Now;
            entry.IsPosted = true;
            DateTime timeStamp = DateTime.Now;
            foreach (var line in entry.Lines)
            {
                line.LastModifiedBy = User.Identity.Name;
                line.LastModifiedDate = timeStamp;
                if (line.Side == NormalSide.Credit)
                {
                    if (line.Account.Type.GetNormalSide() == NormalSide.Credit)
                        line.Account.Balance += line.Amount;
                    else
                        line.Account.Balance -= line.Amount;
                }
                else
                {
                    if (line.Account.Type.GetNormalSide() == NormalSide.Debit)
                        line.Account.Balance += line.Amount;
                    else
                        line.Account.Balance -= line.Amount;
                }
                db.Entry<Account>(line.Account).State = EntityState.Modified;
                db.Entry<JournalEntryLine>(line).State = EntityState.Modified;
            }

            entry.LastModifiedBy = User.Identity.Name;
            entry.LastModifiedDate = timeStamp;
            entry.DatePosted = timeStamp;

            ActivityLog log = new ActivityLog
            {
                CreatedBy = User.Identity.Name,
                LogDate = timeStamp,
                Message = "Posted.",
                ObjectReferenceId = entry.Id,
                ObjectReferenceType = "JournalEntry",
            };
            db.ActivityLogs.Add(log);
            db.Entry<ActivityLog>(log).State = EntityState.Added;
            db.SaveChanges();

            return RedirectToAction("Details", new { id = id });
        }

        public ActionResult SourceDocument(string fileName)
        {
            var path = Server.MapPath(UPLOAD_DIR);
            var file = Path.Combine(path, fileName);

            return File(file, "application/pdf");
        }
	}
}